/**
 * Copyright (c) 2018, 西安星沙网络科技-版权所有
 *
 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.waleychain.exchange.service.impl.wallet;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import cn.waleychain.exchange.core.cache.CacheConts;
import cn.waleychain.exchange.core.constant.DDIC;
import cn.waleychain.exchange.core.constant.OperatePaymentType;
import cn.waleychain.exchange.core.constant.SmsContent;
import cn.waleychain.exchange.core.exec.CoinWalletSyncException;
import cn.waleychain.exchange.core.logger.LoggerHelper;
import cn.waleychain.exchange.core.result.RetResultCode;
import cn.waleychain.exchange.core.utils.BigDecimalUtils;
import cn.waleychain.exchange.core.utils.StringUtils;
import cn.waleychain.exchange.core.vaildate.VaildateHelper;
import cn.waleychain.exchange.dao.CoinWalletChangeMapper;
import cn.waleychain.exchange.dao.CoinWalletMapper;
import cn.waleychain.exchange.dao.WalletRechargeMapper;
import cn.waleychain.exchange.dao.WalletWithdrawMapper;
import cn.waleychain.exchange.feign.CoinServiceFeign;
import cn.waleychain.exchange.feign.CommonServiceFeign;
import cn.waleychain.exchange.feign.UserServiceFeign;
import cn.waleychain.exchange.model.CoinInfo;
import cn.waleychain.exchange.model.CoinWallet;
import cn.waleychain.exchange.model.CoinWalletChange;
import cn.waleychain.exchange.model.UserInfo;
import cn.waleychain.exchange.model.WalletRecharge;
import cn.waleychain.exchange.model.WalletWithdraw;
import cn.waleychain.exchange.service.impl.BaseServiceImpl;
import cn.waleychain.exchange.service.impl.wallet.entity.TransWithdrawParam;
import cn.waleychain.exchange.service.impl.wallet.utils.DefaultCoinClient;
import cn.waleychain.exchange.service.impl.wallet.utils.EthCoinClient;
import cn.waleychain.exchange.service.impl.wallet.utils.EthTokenCoinClient;
import cn.waleychain.exchange.service.wallet.CoinWalletService;

@Service
public class WalletServiceImpl extends BaseServiceImpl {
	
	private static final Logger mLog = LoggerFactory.getLogger(WalletServiceImpl.class);
	
	@Autowired
	private UserServiceFeign userFeign;
	
	@Autowired
	private CoinServiceFeign coinFeign;
	
	@Autowired
	private WalletWithdrawMapper walletWithdrawMapper;
	
	@Autowired
	private WalletRechargeMapper walletRechargeMapper;
	
	@Autowired
	private CoinWalletChangeMapper coinWalletChangeMapper;
	
	@Autowired
	private CoinWalletMapper coinWalletMapper;
	
	@Autowired
	private CoinWalletService coinWalletService;
	
	@Autowired
	private DefaultCoinClient defaultCoinClient;
	
	@Autowired
	private EthCoinClient ethCoinClient;
	
	@Autowired
	private EthTokenCoinClient ethTokenCoinClient;
	
	@Autowired
	private CommonServiceFeign commonFeign;
	
	private ConcurrentHashMap<String, Long> recSuccessTxId = new ConcurrentHashMap<>();
	
	/**
	 * 提现打币
	 * @param transParam
	 * @param isAuto
	 * @return
	 * @throws Exception
	 */
	@Transactional
	public boolean transWithdrawOperate(TransWithdrawParam transParam, boolean isAuto) throws Exception {
		
		// 获取用户信息
		UserInfo user = userFeign.fetchUserInfo(transParam.getUserId());
		VaildateHelper.vaildateBooleanResult(user == null, RetResultCode.E20019, transParam.getUserId());
		
		// 获取币信息
		CoinInfo coin = coinFeign.fetchCoinById(transParam.getCoinId());
		VaildateHelper.vaildateBooleanResult(coin == null, RetResultCode.E30003);
		
		VaildateHelper.vaildateBooleanResult(DDIC.CoinType.WALLET_COIN.id != coin.getCoinType(), RetResultCode.E11001, "钱包币才可以提现");
		
		VaildateHelper.vaildateBooleanResult(transParam.getAmount().compareTo(BigDecimalUtils.genInitValue()) <= 0, RetResultCode.E11001, "提现数量错误");
		
		VaildateHelper.vaildateBooleanResult(StringUtils.isBlank(coin.getRpcIp()) || coin.getRpcPort() == null, RetResultCode.E11001, "未设置RPC服务地址");
		
		CoinWallet coinWallet = coinWalletService.fetchCoinWalletInfo(transParam.getUserId(), coin.getCoinId());
		vaildateCoinWallet(coinWallet, true);
		
		if (DDIC.WalletType.DEFAULT.id == coin.getWalletType()) { // 比特币系列
			if (this.defaultCoinClient.getBalance(coin).doubleValue() < transParam.getAmount().doubleValue()) {
				throw new Exception("钱包余额不足");
			}
			
			if (!this.defaultCoinClient.isValidAddress(coin, transParam.getAddr())) {
				throw new Exception(transParam.getAddr() + "不是一个有效的钱包地址！");
			}
		}
		
		// 自动开关
		boolean autoStatus = coin.getAutoWithdraw().compareTo(transParam.getAmount()) == 1 || coin.getAutoWithdraw().compareTo(transParam.getAmount()) == 0 || isAuto;
		if (!autoStatus) {
			return false;
		}
		
		boolean locked = false;
		try {
			
			if (DDIC.WalletType.DEFAULT.id == coin.getWalletType()) { // 比特币系列
				// 获取同步锁 
				locked = this.redisService.lock(CacheConts.LOCK_COIN_WALLET, String.valueOf(coinWallet.getCoinWalletId()), true);
				if (!locked) {
					throw new CoinWalletSyncException(RetResultCode.E13004);
				}
				
				if (autoStatus) {
					
					// 解冻账号资金
					this.coinWalletService.walletUnfreezeAmount(transParam.getUserId(), transParam.getCoinId(), transParam.getAmount(), 
							OperatePaymentType.TRADE_DETAIL_REMARK_TYPE_WITHDRAWALS.getRemark(), transParam.getOrderId());
					
					// 获取平台账号
					Long platformUserId = userFeign.fetchAdminUserId();
					CoinWallet adminWallet = coinWalletMapper.fetchCoinWalletByCoinIdAndUserId(platformUserId, coin.getCoinId());
					VaildateHelper.vaildateBooleanResult(adminWallet == null, RetResultCode.E40001);
					
					// 扣减用户资金（提现手续费），同时将扣减的资金归到admin账户
					this.coinWalletService.transferAmount(transParam.getCoinWalletId(), adminWallet.getCoinWalletId(), coin.getCoinId(), transParam.getFee(), 
							OperatePaymentType.TRADE_DETAIL_REMARK_TYPE_WITHDRAWALS_POUNDAGE.getRemark(), transParam.getOrderId());
					
					// 用户实际提币到账金额
					BigDecimal realAmount = transParam.getAmount().subtract(transParam.getFee());
					// 扣减用户资金
					this.coinWalletService.withdrawalsAmount(transParam.getCoinWalletId(), coin.getCoinId(), realAmount, OperatePaymentType.TRADE_DETAIL_REMARK_TYPE_WITHDRAWALS_OUT.getRemark());
					
					// 钱包实际打币 
					String sender = "";//this.defaultCoinClient.sendToAddress(coin, user.getUserId(), transParam.getAddr(), transParam.getAmount());
					/*if (sender != null && sender.indexOf("status") != -1) {
						throw new Exception("钱包转出异常：" + sender);
					}*/
					
					// 修改交易id
					WalletWithdraw walletWithdraw = new WalletWithdraw();
					walletWithdraw.setIdNo(transParam.getWithdrawId());
					walletWithdraw.setTxId(sender);
					walletWithdraw.setModifiedTime(new Date());
					
					walletWithdrawMapper.updateByPrimaryKeySelective(walletWithdraw);
					
					// 发送短信
					String smsContent = SmsContent.genWalletWithdraw(transParam.getAmount(), coin.getTitle());
					commonFeign.sendSms(user.getMobile(), smsContent);
				}
				
				// 修改提现状态
				CoinWalletChange cwc = new CoinWalletChange();
				cwc.setIdNo(transParam.getChangeId());
				cwc.setStatus(DDIC.WalletChangeStatus.STATUS_31.id);
				
				coinWalletChangeMapper.updateByPrimaryKeySelective(cwc);
				
			} else {
				// 使用其他方式打币
				// 更爱状态为打币中
				// 修改提现状态
				CoinWalletChange cwc = new CoinWalletChange();
				cwc.setIdNo(transParam.getChangeId());
				cwc.setStatus(DDIC.WalletChangeStatus.STATUS_34.id);
				
				coinWalletChangeMapper.updateByPrimaryKeySelective(cwc);
			}
			
		} catch (Exception e) {
			throw e;
		} finally {
			if (locked) {
				// 释放锁
				this.redisService.unlock(CacheConts.LOCK_COIN_WALLET, String.valueOf(coinWallet.getCoinWalletId()));
			}
		}
		
		return true;
	}
	
	/**
	 * 执行异常，打币失败
	 * @param idNo
	 * @param err
	 * @return
	 * @throws Exception
	 */
	public boolean updateTransOutErrorRemark(Long idNo, String err) {
		
		// 修改交易id
		WalletWithdraw walletWithdraw = new WalletWithdraw();
		walletWithdraw.setIdNo(idNo);
		walletWithdraw.setRemark(err);
		walletWithdraw.setModifiedTime(new Date());
		
		walletWithdrawMapper.updateByPrimaryKeySelective(walletWithdraw);
		
		return true;
	}
	
	public void acceptorInit() throws Exception {
		
		// 获取钱包币
		List<CoinInfo> qbbCoins = coinFeign.fetchCoinList(DDIC.CoinType.WALLET_COIN.id, null, DDIC.CoinStatus.ENABLED.id);
		Iterator<CoinInfo> var2 = qbbCoins.iterator();
//		List<CoinInfo> ethAndToken = new ArrayList<>();
//		CoinInfo ethToken = null;
		while (var2.hasNext()) {
			CoinInfo tradeCoin = var2.next();
			try {
				if (DDIC.WalletType.DEFAULT.id == tradeCoin.getWalletType()) {
					this.coinWalletService.refreshAcceptor(tradeCoin);
				}
//				else if ("eth".equals(tradeCoin.getType())) {
//					ethToken = tradeCoin;
//					ethAndToken.add(tradeCoin);
//				} else if ("ethToken".equals(tradeCoin.getType())) {
//					ethAndToken.add(tradeCoin);
//				}
			} catch (Exception e) {
				LoggerHelper.printLogErrorNotThrows(mLog, e, e.getMessage());
			}
		}
//		if (ethAndToken.size() > 0) {
//			this.refreshAcceptorEthAndToken(ethToken, ethAndToken);
//		}

	}
	
	public void processNotDealReceiveCoin() throws Exception {
		
		// 获取钱包币
		List<CoinInfo> qbbCoins = coinFeign.fetchCoinList(DDIC.CoinType.WALLET_COIN.id, null, DDIC.CoinStatus.ENABLED.id);
		Iterator<CoinInfo> iter1 = qbbCoins.iterator();
		label84:
		while (true) {
			CoinInfo tradeCoin;
			Integer type;
			List<WalletRecharge> notDealList;
			label65:
			do {
				while (iter1.hasNext()) {
					tradeCoin = iter1.next();
					if (tradeCoin != null && !StringUtils.isEmpty(tradeCoin.getRpcIp()) && null != tradeCoin.getRpcPort()) {
						type = tradeCoin.getWalletType();
						Calendar cal = Calendar.getInstance();
						cal.add(Calendar.MINUTE, -3);
						Date beforeDate = cal.getTime();
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd H:m:s");
						LoggerHelper.printLogInfo(mLog, "less than time " + sdf.format(beforeDate));
						notDealList = this.walletRechargeMapper.fetchNotDealInWallet(tradeCoin.getCoinId(), beforeDate);
						
						continue label65;
					}
					LoggerHelper.printLogErrorNotThrows(mLog, null, "RPC no setting, coinId=" + tradeCoin.getCoinId());
				}
				return;
			} while (CollectionUtils.isEmpty(notDealList));

			Iterator<WalletRecharge> iter2 = notDealList.iterator();

			while (true) {
				WalletRecharge inRecord;
				String cacheKey;
				int confirmations;
				while (true) {
					do {
						if (!iter2.hasNext()) {
							continue label84;
						}

						inRecord =  iter2.next();
						cacheKey = tradeCoin.getName() + "|" + inRecord.getTxId() + "|" + inRecord.getAddress();
					} while (this.recSuccessTxId.containsKey(cacheKey));

					try {
						if (DDIC.WalletType.ETH.id != type && DDIC.WalletType.ETH_TOKEN.id != type) {
							confirmations = this.defaultCoinClient.getTransactionConfirmed(tradeCoin, inRecord.getTxId());
							break;
						}

						confirmations = this.ethCoinClient.getTransactionConfirmed(tradeCoin, inRecord.getTxId());
						break;
					} catch (Exception e) {
						LoggerHelper.printLogErrorNotThrows(mLog, e, "coinId=" + tradeCoin.getCoinId() + ",error:" + e.getMessage());
					}
				}

				int confirmedCount = Math.abs(inRecord.getStatus());
				int needConfirmed = confirmations - confirmedCount;

				try {
					if (needConfirmed < 0) {
						CoinWalletChange change = new CoinWalletChange();
						change.setIdNo(inRecord.getWalletChangeId());
						change.setStatus(needConfirmed);
						
						this.coinWalletChangeMapper.updateByPrimaryKeySelective(change);
						LoggerHelper.printLogInfo(mLog, "receive userId=>" + inRecord.getUserName() + " " + tradeCoin.getName() + " " + inRecord.getAmount().toPlainString() + ", but confirmations not enough");
					} else {
						CoinWalletChange change = new CoinWalletChange();
						change.setIdNo(inRecord.getWalletChangeId());
						change.setStatus(DDIC.WalletChangeStatus.RECHARGE_SUSS.id);
						
						this.coinWalletChangeMapper.updateByPrimaryKeySelective(change);
						this.coinWalletService.rechargeAmount(inRecord.getUserId(), tradeCoin.getCoinId(), inRecord.getAmount(), OperatePaymentType.TRADE_DETAIL_REMARK_TYPE_RECHARGE_INTO.getRemark());
						this.recSuccessTxId.put(cacheKey, System.currentTimeMillis());
						LoggerHelper.printLogInfo(mLog, "processNotDealReceiveCoin");

						try {
							if (DDIC.WalletType.DEFAULT.id == type) {
								LoggerHelper.printLogInfo(mLog, "default开始归账,用户Id是:" + inRecord.getUserId());
								this.defaultCoinClient.addCollectTask(tradeCoin, inRecord.getUserId(), inRecord.getAmount());
							} else if (DDIC.WalletType.ETH.id == type) {
								LoggerHelper.printLogInfo(mLog, "eth开始归账,用户Id是:" + inRecord.getUserId());
								this.ethCoinClient.addCollectTask(tradeCoin, inRecord.getUserId(), inRecord.getAmount());
							} else if (DDIC.WalletType.ETH_TOKEN.id == type) {
								LoggerHelper.printLogInfo(mLog, "ethToken开始归账,用户Id是:" + inRecord.getUserId());
								this.ethTokenCoinClient.addCollectTask(tradeCoin, inRecord.getUserId(), inRecord.getAmount());
							}
						} catch (Exception e) {
							LoggerHelper.printLogErrorNotThrows(mLog, e, "add collect task error:" + e.getMessage());
						}
					}
				} catch (Exception e) {
					LoggerHelper.printLogErrorNotThrows(mLog, e, "receive error rollback:" + e.getMessage());
				}
			}
		}
	}
	
	public void cleanRecSuccessTxId() {
		if (this.recSuccessTxId != null && this.recSuccessTxId.size() >= 50000) {
			this.recSuccessTxId.clear();
			LoggerHelper.printLogInfo(mLog, "clean all cache recSuccessTxId.");
		}
	}
	
	/**
	 * 钱包提现打币线程任务
	 * @author chenx
	 * @date 2018年4月17日 下午6:49:22
	 * @version 1.0WalletServiceUtils
	 */
	static class TransOutTask implements Runnable {
		
		private TransWithdrawParam param;
		private WalletServiceImpl service;
		private boolean auto;

		public TransOutTask(TransWithdrawParam param, WalletServiceImpl service, boolean auto) {
			this.param = param;
			this.service = service;
			this.auto = auto;
		}

		public void run() {
			try {
				this.service.transWithdrawOperate(this.param, this.auto);
			} catch (Exception e) {
				e.printStackTrace();
				String error = e.getMessage().substring(0, e.getMessage().length() > 100 ? 100 : e.getMessage().length());
				// 更新备注
				this.service.updateTransOutErrorRemark(this.param.getWithdrawId(), error);
			}
		}
	}
}
